/**
 * Copyright (C) 2009 Noel Ang
 *
 * This file is part of Samsara.
 *
 * Samsara is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Samsara is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Samsara.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package com.lunarshades.samsara;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import com.lunarshades.samsara.ui.Filterable;
import com.lunarshades.samsara.ui.TableColumn;
import com.lunarshades.samsara.ui.Utils;

/**
 * Represents an obnoxious loot whore or competitive player, depending on your
 * point of view.
 *
 * @author Noel Ang, sparta.variable@gmail.com
 */
public class Raider implements Comparable<Raider>, Filterable {

public Raider() {
    this(null);
}

public Raider(String id) {
    if (id == null || "".equals(id.trim().intern())) {
        id = "";
    }
    mId = mDisplayName = capitalize(clean(id));
    mHashCode = mId.toUpperCase().hashCode();
    mKarmaMap.put(DEFAULT_KARMA_CONTEXT, new Karma(0));
    mDisplayNames.add(mDisplayName);
}

private String capitalize(String content) {
    assert content != null;
    StringBuffer buffer = new StringBuffer();
    for (int i = 0, len = content.length(); i < len; ++i) {
        char ch = content.charAt(i);
        buffer.append((i == 0
                       ? Character.toUpperCase(ch)
                       : Character.toLowerCase(ch))
        );
    }
    return buffer.toString().trim();
}

private String clean(String context) {
    assert context != null;
    StringBuffer buffer = new StringBuffer();
    final String FORBIDDEN_CHARACTERS = "(),:;/";
    for (int i = 0, len = context.length(); i < len; ++i) {
        char ch = context.charAt(i);
        if (FORBIDDEN_CHARACTERS.indexOf(ch) == -1) {
            buffer.append(ch);
        }
    }
    return buffer.toString().trim();
}

private String findMatchingName(String name) {
    synchronized (mDisplayNames) {
        for (String n : mDisplayNames) {
            if (n.equalsIgnoreCase(name)) {
                return n;
            }
        }
    }
    return null;
}

public String id() {
    return mId;
}

@TableColumn(0)
public String name() {
    return mDisplayName;
}

public void name(String name) {
    if ("".equals((name = capitalize(clean(name))))) {
        return;
    }

    synchronized (mDisplayNames) {
        if ("".equals(mDisplayName)) {
            mDisplayNames.remove("");
            mId = name;
            mHashCode = mId.toUpperCase().hashCode();
        }
        if (findMatchingName(name) == null) {
            mDisplayNames.add(name);
        }
        mDisplayName = name;
    }
}

public Set<String> names() {
    synchronized (mDisplayNames) {
        return Collections.unmodifiableSet(mDisplayNames);
    }
}

public void unname(String name) {
    if ("".equals((name = capitalize(clean(name))))) {
        return;
    }
    if (mId.equalsIgnoreCase(name)) {
        return;
    }

    synchronized (mDisplayNames) {
        String match;
        if ((match = findMatchingName(name)) != null) {
            if (mDisplayNames.size() > 1) {
                mDisplayNames.remove(match);
                if (match.equalsIgnoreCase(mDisplayName)) {
                    mDisplayName = mDisplayNames.iterator().next();
                }
            }
        }
    }
}

public boolean hasName(String name) {
    return findMatchingName(capitalize(clean(name))) != null;
}

@TableColumn(1)
public Karma karma() {
    return mKarmaMap.get(DEFAULT_KARMA_CONTEXT);
}

public void karma(final Karma karma) {
    if (karma != null) {
        mKarmaMap.put(DEFAULT_KARMA_CONTEXT, karma);
    }
}

public Karma karma(final String context) {
    String aContext = DEFAULT_KARMA_CONTEXT;
    if (context != null) {
        aContext = clean(context).intern();
    }
    return mKarmaMap.get(aContext);
}

public void setKarma(final String context, final Karma karma) {
    String aContext = DEFAULT_KARMA_CONTEXT;

    if (karma != null) {
        if (context != null) {
            aContext = clean(context).intern();
        }
        mKarmaMap.put(aContext, karma);
    }
}

@TableColumn(2)
public boolean pickup() {
    return mIsPickup;
}

public void setPickup(boolean isPickup) {
    mIsPickup = isPickup;
}

@Override
public int hashCode() {
    return mHashCode;
}

@Override
public boolean equals(Object obj) {
    return !(obj == null || !(obj instanceof Raider))
            && (obj == this || ((Raider) obj).mId.equalsIgnoreCase(mId));
}

public void setIsIncluded(boolean filtered) {
    mIsIncluded = filtered;
}

public boolean isIncluded() {
    return mIsIncluded;
}

public int compareTo(Raider that) {
    if (that == null) {
        return 1;
    }
    return id().compareToIgnoreCase(that.id());
}

@Override
public String toString() {
    return name();
}

public String marshal() throws IOException {
    // Format:
    // name1/name2/name3/...,(context1:karmaQty1;context2:karmaQty2;context3:karmaQty3)...)
    StringBuffer marshalBuffer = new StringBuffer();
    marshalBuffer.append(mId);
    if (mDisplayNames.size() > 1) {
        synchronized (mDisplayNames) {
            for (String name : mDisplayNames) {
                if (!name.equalsIgnoreCase(mId)) {
                    marshalBuffer.append(MARSHALING_NAMES_DELIMITER);
                    marshalBuffer.append(name);
                }
            }
        }
    }
    marshalBuffer.append(MARSHALING_DELIMITER);
    marshalBuffer.append('(');
    synchronized (mKarmaMap) {
        for (Map.Entry<String, Karma> karmaEntry : mKarmaMap.entrySet()) {
            String context = karmaEntry.getKey();
            Karma karma = karmaEntry.getValue();
            marshalBuffer.append(context);
            marshalBuffer.append(':');
            marshalBuffer.append(karma.toString());
            marshalBuffer.append(';');
        }
    }
    marshalBuffer.deleteCharAt(marshalBuffer.length() - 1).append(')');
    return marshalBuffer.toString();
}

public void unmarshal(String serialized) throws IOException {
    String primaryName = "";
    Set<String> allNames = new HashSet<String>();
    Map<String, Karma> allKarmas = new HashMap<String, Karma>();
    
    // Format:
    // name1/name2/name3/...,(context1:karmaQty1;context2:karmaQty2;context3:karmaQty3)...)
    StringTokenizer tokenizer =
            new StringTokenizer(serialized, MARSHALING_DELIMITER);

    // Parse Name
    if (!tokenizer.hasMoreTokens()) {
        throw new IOException("Bad file format: Foreign line: " + serialized);
    } else {
        StringTokenizer nameTokenizer =
                new StringTokenizer(tokenizer.nextToken(),
                        MARSHALING_NAMES_DELIMITER
                );
        
        if (nameTokenizer.hasMoreTokens()) {
            primaryName = capitalize(clean(nameTokenizer.nextToken()));
        }

        while (nameTokenizer.hasMoreTokens()) {
            allNames.add(nameTokenizer.nextToken());
        }
    }

    // Parse Karma spec
    if (!tokenizer.hasMoreTokens()) {
        throw new IOException("Bad file format: Missing Token Spec for entry:\n"
                + name()
        );
    } else {
        String context = tokenizer.nextToken().trim();
        int length = context.length();
        int endOffset = context.length() - 1;

        if (length <= 1
                || context.charAt(0) != '('
                || context.charAt(endOffset) != ')') {
            throw new IOException(
                    "Bad file format: Malformed karma spec area:\n" + context
            );
        }
        tokenizer = new StringTokenizer(context.substring(1, endOffset), ";");
    }

    // Parse individual Karma counts
    while (tokenizer.hasMoreTokens()) {
        String countSpec = tokenizer.nextToken().trim();
        StringTokenizer karmaTokenizer = new StringTokenizer(countSpec, ":");
        if (karmaTokenizer.countTokens() == 2) {
            String context = clean(karmaTokenizer.nextToken());
            String karmaPoints = karmaTokenizer.nextToken().trim();
            int points;
            try {
                points = Utils.makeInt(karmaPoints);
            } catch (NumberFormatException e) {
                throw new IOException(
                        "Bad file format: Malformed karma tuple:\n" + countSpec
                );
            }
            Karma karma = new Karma(points);
            allKarmas.put(context, karma);

        } else if (karmaTokenizer.countTokens() != 0) {
            throw new IOException("Bad file format: Malformed karma tuple:\n"
                    + countSpec
            );
        }
    }

    // Unmarshal successful; finalize
    mId = mDisplayName = primaryName;
    synchronized (mDisplayNames) {
        mDisplayNames.clear();
        mDisplayNames.add(mDisplayName);
        mDisplayNames.addAll(allNames);
    }
    synchronized (mKarmaMap) {
        mKarmaMap.clear();
        mKarmaMap.putAll(allKarmas);
    }
}

private static final String MARSHALING_DELIMITER = ",";
private static final String MARSHALING_NAMES_DELIMITER = "/";
private static final String DEFAULT_KARMA_CONTEXT = "default";

private volatile String mId;
private volatile String mDisplayName;
private volatile int mHashCode;
private volatile boolean mIsIncluded = true;
private volatile boolean mIsPickup;
private final Map<String, Karma> mKarmaMap =
        Collections.synchronizedMap(new HashMap<String, Karma>());
private final Set<String> mDisplayNames =
        Collections.synchronizedSet(new HashSet<String>());
}
